<?php
/**
 * Fastapp.
 * 7/23/24
 * @Author 深色的海 (wx:deepsea_0024)
 * @Contact telegram:deepsea159
 */

use App\Fastapp\Exception\TipException;
use App\Fastapp\FastappApiCacheService;
use App\Fastapp\Model\AccountUploadfile;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Filesystem\FilesystemFactory;
use Hyperf\HttpMessage\Upload\UploadedFile;
use Hyperf\Logger\LoggerFactory;
use Intervention\Image\Drivers\Imagick\Driver;
use Intervention\Image\ImageManager;
use League\Flysystem\Filesystem;
use MessageNotify\Notify;
use MessageNotify\Template\Text;
use Mine\Helper\Str;
use Mine\MineRequest;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\SimpleCache\CacheInterface;
use Swoole\Coroutine;
use Telegram\Bot\Api;

if (!function_exists('fastapp_log')) {
    function fastapp_log(): LoggerFactory
    {
        return container()->get(LoggerFactory::class);
    }
}

if (!function_exists('fastapp_header')) {
    function fastapp_header(string $key = '', string|int $default = ''): array|string
    {
        return container()->get(MineRequest::class)->header($key, $default);
    }
}

if (!function_exists('fastapp_lang')) {
    function fastapp_lang(): string
    {
        $acceptLanguage = fastapp_header('accept-language') ?: fastapp_header('Accept-Language');
        return str_replace('-', '_', $acceptLanguage ?: 'zh_Hans');
    }
}

if (!function_exists('fastapp_shop')) {
    function fastapp_shop(int $admin_id): void
    {
        if (class_exists(\App\Mall\Model\MallShop::class)) {
            $shops = \App\Mall\Model\MallShop::query()->where(['admin_id' => $admin_id])->orderBy('id')->pluck('id');
            if ($shops->count()) $res = ['shop_id' => $shops->implode(','), 'main_shop_id' => $shops[0]];
            else {
                $md = \App\Mall\Model\MallShop::query()->create(['admin_id' => $admin_id, 'created_by' => $admin_id, 'updated_by' => $admin_id, 'name' => '默认店铺']);
                $res = ['shop_ids' => $md->id, 'main_shop_id' => $md->id];
            }
            redis()->hMSet('adm:' . $admin_id, $res);
        }
    }
}

if (!function_exists('fastapp_lang_text')) {
    function fastapp_lang_text(array|null|string $data, string $lang = ''): string
    {
        if (!$data) return '';
        if (is_string($data)) return fastapp_t($data);
        $res = array_column($data, 'text', 'lang');
        return $res[$lang ?: fastapp_lang()] ?? $res['all'] ?? '';
    }
}

if (!function_exists('fastapp_t')) {
    function fastapp_t(string $key, array $replace = [], string $lang = ''): string
    {
        return \Hyperf\Translation\__($key, $replace, $lang ?: fastapp_lang());
    }
}

if (!function_exists('calculate_array_num')) {
    function calculate_array_num(&$node, string $field): float|int
    {
        $total = $node[$field];
        if (!empty($node['children'])) {
            foreach ($node['children'] as &$child) {
                $total += calculate_array_num($child, $field);
            }
        }
        $node[$field] = $total;
        return $total;
    }
}


if (!function_exists('fastapp_queue_push')) {
    function fastapp_queue_push(\Hyperf\AsyncQueue\JobInterface $job, string $queue = 'default'): bool
    {
        return container()->get(\Hyperf\AsyncQueue\Driver\DriverFactory::class)->get($queue)->push($job);
    }
}

if (!function_exists('get_uid')) {
    function get_uid(string $scene = ''): int
    {
        if (!$scene) $scene = fastapp_header('app-source', 'api');
        return user($scene)->getId();
    }
}

if (!function_exists('get_filesystem')) {
    function get_filesystem(): array
    {
        $mode = sys_config('upload_mode')['value'];
        $adapterName = match ($mode) {
            2 => 'oss',
            3 => 'qiniu',
            4 => 'cos',
            default => 'local'
        };
        $filesystem = container()->get(FilesystemFactory::class)->get($adapterName);
        return [$filesystem, $mode, $adapterName];
    }
}

if (!function_exists('upload_file')) {
    function upload_file(UploadedFile $file, string $dir = '', bool $check = true): array
    {
        try {
            $hash = md5_file($file->getPath() . '/' . $file->getFilename());
        } catch (Exception) {
            throw new TipException(fastapp_t('fastapp.failed_to_retrieve_file_hash'));
        }
        $md = AccountUploadfile::query()->where(['hash' => $hash])->first(['id', 'hash', 'path', 'storage_mode']);
        $local_path = BASE_PATH . '/public/' . env('UPLOAD_PATH', 'uploadfile');
        if ($md) {
            if ($check) {
                $data = $md->toArray();
                $data['url'] = get_file_url($md->path);
                return $data;
            } else {
                if ($md->storage_mode == 1) @unlink($local_path . $md->path);
            }
        }
        /** @var Filesystem $filesystem */
        [$filesystem, $mode] = get_filesystem();
        $suffix = $file->getExtension();
        if (strlen($suffix) > 10) {
            if (str_contains($file->getClientMediaType(), 'png')) $suffix = 'png';
            elseif (str_contains($file->getClientMediaType(), 'jpeg')) $suffix = 'jpeg';
            else throw new TipException(fastapp_t('fastapp.file_cannot_be_recognized'));
        }
        $filename = snowflake_id() . '.' . Str::lower($suffix);
        $path = $dir . '/' . date('Ymd') . '/' . $filename;
        if (!$filesystem->fileExists($path)) {
            try {
                if (!$filesystem->directoryExists(dirname($path))) $filesystem->createDirectory(dirname($path), ['visibility' => 'public']);
                $filesystem->writeStream($path, $file->getStream()->detach());
            } catch (Exception) {
                throw new TipException((string)$file->getError(), 500);
            }
        }
        $file_path = $local_path . $path;
        if ($mode == 1 && str_starts_with($file->getMimeType(), 'image')) image_compression($file_path, 800);
        $fileInfo = [
            'storage_mode' => $mode,
            'origin_name' => $file->getClientFilename(),
            'object_name' => $filename,
            'mime_type' => $file->getClientMediaType(),
            'storage_path' => $path,
            'hash' => $hash,
            'suffix' => Str::lower($suffix),
            'size_byte' => filesize($file_path),
            'path' => $path,
        ];
        if (!$md) $md = new AccountUploadfile();
        $md->fill($fileInfo);
        $md->save();
        return [
            'id' => $md->id,
            'hash' => $hash,
            'path' => $md->path,
            'url' => get_file_url($md->path),
        ];
    }
}

if (!function_exists('image_compression')) {
    function image_compression(string $file_path, int $size, bool $del = false): void
    {
        $manager = new ImageManager(new Driver());
        try {
            $image = $manager->read($file_path);
            start:
            $filesize = filesize($file_path) / 1024;
            if ($filesize > $size) {
                $image->resize((int)($image->width() / 2), (int)($image->height() / 2));
                $image->save($file_path);
                goto start;
            }
            if ($del) unlink($file_path);
        } catch (\Throwable) {
            unlink($file_path);
            throw new TipException(fastapp_t('fastapp.file_upload_failed', ['name' => fastapp_t('fastapp.image')]));
        }
    }
}

if (!function_exists('base642image')) {
    function base642image(string $content, string $dir, string $filename = '', bool $is_binary = false, int $uid = 0): string
    {
        if (!$content) return '';
        /** @var Filesystem $filesystem */
        [$filesystem, $mode] = get_filesystem();
        $filename = $filename ?: snowflake_id() . '.png';
        $path = $dir . '/' . $filename;
        $resource = fopen('php://temp', 'rw+');
        if (!$is_binary) $content = base64_decode($content);
        $hash = md5($content);
        fwrite($resource, $content);
        $finfo = fstat($resource);
        $md = AccountUploadfile::query()->where(['hash' => $hash])->first(['path', 'hash']);
        if ($md) return get_file_url($md->path);

        $fileInfo = [
            'storage_mode' => $mode,
            'origin_name' => $filename,
            'object_name' => $filename,
            'hash' => $hash,
            'size_byte' => $finfo['size'],
            'path' => $path,
            'mime_type' => 'image/png',
            'suffix' => 'png',
        ];
        $fileInfo['user_id'] = $uid ?: get_uid();
        AccountUploadfile::query()->create($fileInfo);
        $filesystem->writeStream($path, $resource);
        fclose($resource);
        return $path;
    }
}

if (!function_exists('get_file_url')) {
    function get_file_url(string $path = ''): string
    {
        if (str_starts_with($path, 'http')) return $path;
        [, , $key] = get_filesystem();
        if ($key == 'local') {
            $domain = sys_config('file_domain')['value'];
            $subDir = '/' . env('UPLOAD_PATH', 'uploadfile');
            if ($path && !str_starts_with($path, $subDir)) $domain .= $subDir;
        } else $domain = sys_config($key . '_domain')['value'];
        if ($path) return $domain . $path;
        return $domain;
    }
}

if (!function_exists('num_to_chinese')) {
    /**
     * 金额数字转中文大写
     * @param string $num
     * @return string
     */
    function num_to_chinese(string $num): string
    {
        $unit = ['厘', '分', '角', '元', '十', '百', '千', '万', '亿'];
        $char = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
        $result = '';
        $num = str_split($num);
        $len = count($num);
        for ($i = 0; $i < $len; $i++) {
            $bit = intval($num[$i]);
            if ($bit > 0) {
                $result .= $char[$bit] . $unit[$len - $i - 1];
            } else {
                if (!str_ends_with($result, '零')) {
                    $result .= '零';
                }
            }
        }
        return rtrim($result, '零');
    }
}

if (!function_exists('sys_notify')) {
    /**
     * 系统错误信息
     * @param string $cnt
     * @param array $config
     * @return void
     */
    function sys_notify(string $cnt, array $config = []): void
    {
        $config = $config ?: api_cache()->getConfigByGroupKey('system_config');
        if (empty($config['notify_robot_type']) || empty($config['notify_robot_token'])) return;
        $cnt = env('APP_NAME') . ' | ' . $cnt;
        if ($config['notify_robot_type'] == 4) {
            Coroutine::create(function () use ($cnt, $config) {
                $c = explode(',', $config['notify_robot_token']);
                (new Api($c[0]))->sendMessage([
                    'chat_id' => $c[1],
                    'text' => $cnt
                ]);
            });
            return;
        }
        if ($config['notify_robot_type'] == 1) $cs = \MessageNotify\Channel\WechatChannel::class;
        elseif ($config['notify_robot_type'] == 2) $cs = \MessageNotify\Channel\FeiShuChannel::class;
        else $cs = \MessageNotify\Channel\DingTalkChannel::class;
        Coroutine::create(function () use ($cnt, $cs, $config) {
            $k = 'message.channels.' . $cs;
            $data = container()->get(ConfigInterface::class)->get($k);
            if (empty($data['pipeline']['info']['token'])) {
                $data['pipeline']['info']['token'] = $config['notify_robot_token'];
                container()->get(ConfigInterface::class)->set($k, $data);
            }
            Notify::make()->setChannel($cs)
                ->setTemplate(Text::class)
                ->setText($cnt)
                ->send();
        });
    }
}

if (!function_exists('getDistance')) {
    /**
     * 腾讯地图两点距离计算
     * @param $lat1
     * @param $lng1
     * @param $lat2
     * @param $lng2
     * @return float|int 米
     */
    function getDistance($lat1, $lng1, $lat2, $lng2): float|int
    {
        // 将角度转换为弧度
        $radLat1 = deg2rad($lat1);
        $radLat2 = deg2rad($lat2);
        $a = $radLat1 - $radLat2;
        $b = deg2rad($lng1) - deg2rad($lng2);
        $s = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2)));
        $s = $s * 6378.137; // 地球半径为 6378.137 km
        // 保留四位小数
        return round($s * 10000) / 10;
    }
}

if (!function_exists('api_cache')) {
    function api_cache(): FastappApiCacheService
    {
        return container()->get(FastappApiCacheService::class);
    }
}

if (!function_exists('api_dispatch')) {
    function api_dispatch(object $event): void
    {
        go(function () use ($event) {
            container()->get(EventDispatcherInterface::class)->dispatch($event);
        });
    }
}

if (!function_exists('set_user_cache')) {
    function set_user_cache(array $data, string|int $uid = 0): bool
    {
        if (empty($uid)) $uid = get_uid();
        return (bool)redis()->hMSet('u:' . $uid, $data);
    }
}

if (!function_exists('user_lock')) {
    function user_lock(string|int $uid = 0, int $tm = 5): bool
    {
        if (empty($uid)) $uid = get_uid();
        $s = redis()->hSetNx('ul:' . $uid, 'lock', 1);
        if ($s) redis()->expire('ul:' . $uid, $tm);
        return $s;
    }
}

if (!function_exists('user_unlock')) {
    function user_unlock(string|int $uid = 0): bool
    {
        if (empty($uid)) $uid = get_uid();
        return redis()->hDel('ul:' . $uid, 'lock');
    }
}

if (!function_exists('get_user_cache')) {
    function get_user_cache(string|array $f = '', string|int $uid = 0): string|array
    {
        if (empty($uid)) $uid = get_uid();
        if ($f) {
            if (is_array($f)) return redis()->hMGet('u:' . $uid, $f);
            return redis()->hGet('u:' . $uid, $f) ?: '';
        }
        return redis()->hGetAll('u:' . $uid);
    }
}
if (!function_exists('getClientInfo')) {
    function getClientInfo(): array
    {
        $request = container()->get(MineRequest::class);
        $agent = $request->getHeader('user-agent')[0];
        $ip = $request->ip();
        try {
            $region = Str::ipToRegion($ip);
        } catch (\Throwable) {
            $region = '';
        }
        return [$ip, $region, $agent];
    }
}

if (!function_exists('maskMiddleChars')) {
    /**
     * 中间字符串替换
     * @param string|null $str
     * @param int $num
     * @return string
     */
    function maskMiddleChars(?string $str, int $num): string
    {
        if (!$str) return '';
        $length = strlen($str);
        if ($length <= $num) {
            return str_repeat('*', $length);
        }

        $startPos = floor(($length - $num) / 2);
        return substr($str, 0, $startPos) . str_repeat('*', $num) . substr($str, $startPos + $num);
    }
}
if (!function_exists('nfmt')) {
    function nfmt(string|int $n, int $dec = 2): string
    {
        return number_format((float)$n, $dec, '.', '');
    }
}
if (!function_exists('randomByProbability')) {
    /**
     * 随机生成数字函数
     *
     * @param array $ranges 包含随机数范围和对应概率的数组。例如：
     * [
     *     [100, 5000, 70],  // 100-5000 概率 70%
     *     [5001, 8000, 20], // 5001-8000 概率 20%
     *     [8001, 9999, 10]  // 8001-9999 概率 10%
     * ]
     *
     * @return int|float 返回随机生成的数字
     * @throws Exception
     */
    function randomByProbability(array $ranges): int|float
    {
        $probabilitySum = 0;
        $probabilityMap = [];
        foreach ($ranges as $range) {
            list($min, $max, $probability) = $range;
            $probabilitySum += $probability;
            $probabilityMap[] = [
                'min' => $min,
                'max' => $max,
                'cumulative' => $probabilitySum
            ];
        }
        if ($probabilitySum !== 100) {
            throw new Exception("概率总和必须为 100");
        }
        $randomProbability = mt_rand(1, 100);
        foreach ($probabilityMap as $range) {
            if ($randomProbability <= $range['cumulative']) {
                return mt_rand((int)($range['min'] * 100), (int)($range['max'] * 100)) / 100;
            }
        }
        return 0;
    }
}

if (!function_exists('cache')) {
    function cache(): CacheInterface
    {
        return container()->get(CacheInterface::class);
    }
}

if (!function_exists('getParentNodes')) {
    function getParentNodes(array $tree, int|string $childNodeId, string $field = 'parent_id'): array
    {
        $map = array_combine(array_column($tree, 'id'), array_column($tree, $field));
        return getIdAndPid($map, $childNodeId);
    }

    function getIdAndPid(&$map, $id): array
    {
        $res = [];
        joinPid($map, $id, $res);
        return array_values(array_unique($res));
    }

    function joinPid(&$map, $id, &$res): void
    {
        if (isset($map[$id]) && $map[$id] != 0) {
            joinPid($map, $map[$id], $res);
        }
        $res[] = $id;
    }


    function getLeafIds($tree, &$leafIds = [], string $field = 'id')
    {
        foreach ($tree as $node) {
            if (!empty($node['children']) && is_array($node['children'])) {
                getLeafIds($node['children'], $leafIds);
            } else {
                if (isset($node[$field])) {
                    $leafIds[] = $node[$field];
                }
            }
        }
        return $leafIds;
    }
}

if (!function_exists('set_currency_cache')) {
    function set_currency_cache(): void
    {
        $field = ['id', 'symbol', 'is_show', 'type', 'icon', 'address', 'withdrawal_min', 'withdrawal_max', 'deposit_min', 'recharge_min', 'precision', 'trade_precision', 'withdrawal_charge'];
        redis()->hMSet('currency', \App\Fastapp\Model\Currency::query()->get($field)->mapWithKeys(function ($item) {
            return [$item['symbol'] => serialize($item->toArray())];
        })->toArray());
    }
}

if (!function_exists('get_currency_cache')) {
    function get_currency_cache(string $symbol = ''): ?array
    {
        if ($symbol) {
            $v = redis()->hGet('currency', $symbol);
            return unserialize($v);
        }
        $res = [];
        foreach (redis()->hGetAll('currency') as $k => $v) {
            $res[$k] = unserialize($v);
        }
        return $res;
    }
}

if (!function_exists('html_format')) {
    function html_format($html, $replacements): string
    {
        $s = str_replace('%', '%%', $html);
        $s = str_replace('%%s', '%s', $s);
        return vsprintf($s, $replacements);
    }
}

if (!function_exists('resetFileName')) {
    function resetFileName(string $real_path, string $name = ''): string|bool
    {
        $info = pathinfo($real_path);
        if (!$name) $name = md5_file($real_path);
        $fname = $info['dirname'] . '/' . $name . '.' . $info['extension'];
        if (file_exists($fname)) {
            @unlink($real_path);
            return $fname;
        }
        if (rename($real_path, $fname)) return $fname;
        return false;
    }
}

$dir = BASE_PATH . '/app';
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
    if ($file != "." && $file != ".." && file_exists($dir . '/' . $file . '/Fun.php')) {
        require $dir . '/' . $file . '/Fun.php';
    }
}
closedir($dh);
